package com.cmb.lk50.common;

import com.cmb.lk50.common.extractor.QueryParamExtractor;
import com.cmb.lk50.common.receiver.HttpStatusReceiver;
import com.cmb.lk50.common.receiver.ResponseReceiver;
import com.cmb.lk50.pojo.AbstractLog;
import com.cmb.lk50.pojo.AccessInfo;
import com.cmb.lk50.sink.LogSink;
import com.cmb.lk50.sink.kafka.KafkaKeyGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;

/**
 * Servlet容器的Http接口访问记录主流程
 *
 * Author       : 蒋俊钊 80256973
 * Date         : 2019/4/11
 * Copyright    (C) ChinaMerchantsBank
 */
public class AccessInterceptor extends Sinkable implements HandlerInterceptor {

    private static ThreadLocal<AccessInfo> threadLocal = new ThreadLocal<>();

    private static HttpStatusReceiver httpStatusReceiver = new HttpStatusReceiver();

    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Autowired(required = false)
    private List<LogSink> logSinks;

    @Resource
    private Map<Class, QueryParamExtractor> cacheExtractor;

    @Resource
    private Map<Class, ResponseReceiver> cacheReceiver;

    @Resource
    private Map<Class, KafkaKeyGenerator<AbstractLog>> cacheKeyer;

    // 请求处理前调用
    // 返回false会视为请求结束，不再处理
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;

            AccessLog annotation = handlerMethod.getMethod().getAnnotation(AccessLog.class);
            /*// 目前只支持方法上的注解，在类上注解会被忽略
            if (annotation == null) {
                annotation = handlerMethod.getMethod().getDeclaringClass().getAnnotation(AccessLog.class);
            }*/

            if (annotation != null) {
                AccessInfo accessInfo = new AccessInfo();
                accessInfo.setHostName(request.getServerName());
                accessInfo.setHostIp(request.getLocalAddr());
                accessInfo.setHostPort(request.getLocalPort());

                accessInfo.setMethod(request.getMethod());
                accessInfo.setPath(request.getRequestURI());

                accessInfo.setCostTime(System.currentTimeMillis());

                String invokeIP;
                String forwardIP = (String) request.getAttribute("x-forwarded-for");
                if (StringUtils.isEmpty(forwardIP)) {
                    invokeIP = request.getRemoteHost();
                } else {
                    invokeIP = forwardIP.contains(",") ? forwardIP.split(",")[0] : forwardIP;
                }
                accessInfo.setInvokeIp(invokeIP);
                accessInfo.setInvokePort(request.getRemotePort());

                // 不能依据GET请求或POST请求判断参数在哪里
                QueryParamExtractor extractor = cacheExtractor.get(annotation.extractor());
                if (extractor != null) {
                    String queryParam = extractor.extract(request);
                    accessInfo.setQueryParam(queryParam);
                }

                threadLocal.set(accessInfo);
            }
        }
        return true;
    }

    // controller之后调用，dispatchServlet返回渲染视图前被调用
    // 先声明的拦截器，postHandle方法后执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        AccessInfo accessInfo =  threadLocal.get();
        threadLocal.remove();  // todo controller中抛异常，看是否会内存泄漏

        if (handler instanceof HandlerMethod && accessInfo != null) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            AccessLog annotation = handlerMethod.getMethod().getAnnotation(AccessLog.class);

            if (annotation != null) {
                long costTime = System.currentTimeMillis() - accessInfo.getCostTime();
                accessInfo.setCostTime(costTime);

                ResponseReceiver receiver = cacheReceiver.get(annotation.receiver());
                if (receiver != null && !(receiver instanceof HttpStatusReceiver)) {
                    accessInfo.setResponseContent(receiver.receive(response));
                }
                accessInfo.setStatus(httpStatusReceiver.receive(response));
                accessInfo.setLogTime(DATE_TIME_FORMATTER.format(LocalDateTime.now()));

                AbstractLog log;
                Class<? extends AbstractLog> logClazz = annotation.value();
                if (logClazz != AccessInfo.class) {
                    log = logClazz.newInstance().convert(accessInfo);
                } else {
                    log = accessInfo;
                }

                doSinks(logSinks, log, cacheKeyer.get(annotation.kafkaKeyer()));
            }
        }
    }

    // 请求结束之后，dispatchServlet返回渲染视图后被调用
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }

}